using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PentaBiz.Collections.DataCache;

namespace PentaBiz.Testing.Collections.DataCache
{
    [TestClass]
    public class UnitTestSequentialDataCache30000Query
    {
        private static SequentialDataCache<MyData> _sequentialDataCache;

        private const int PageSize = 10;

        [ClassInitialize]
        public static void Initialize(TestContext context)
        {
            _sequentialDataCache = SequentialDataCache<MyData>.Initialize(PageSize, deleteOnClose: true);

            for (int i = 0; i < 30000; i++)
            {
                var d = new MyData
                            {
                                Index = i,
                                Id = Guid.NewGuid(),
                                TwoDimensionDoubleArray = MyData.InitializeTwoDimensionDoubleArray(),
                                //TwoDimensionDecimalArray = MyData.InitializeTwoDimensionDecimalArray(),
                            };

                _sequentialDataCache.Add(d);
            }

            if (File.Exists("profile.txt"))
                File.Delete("profile.txt");
        }

        [ClassCleanup]
        public static void Cleanup()
        {
            string path = _sequentialDataCache.Path;
            _sequentialDataCache.Close();
        }

        [TestInitialize]
        public void BeforeTest()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }

        [TestCleanup]
        public void AfterTest()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestIndexAccess0()
        {
            //Assert.IsTrue(File.Exists(collection.Path));
            var sw = new Stopwatch();
            sw.Start();

            MyData d = _sequentialDataCache[0];
            sw.Stop();

            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(d);
            Assert.AreEqual(0, d.Index);
        }

        private void WriteResult(TimeSpan ellapsed, [CallerMemberName] string caller = "")
        {
            File.AppendAllText("Profile.txt", string.Format("* {0}: *{1}*\r\n", caller, ellapsed));
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestIndexAccess500()
        {
            //Assert.IsTrue(File.Exists(collection.Path));
            var sw = new Stopwatch();
            sw.Start();

            MyData d = _sequentialDataCache[500];
            sw.Stop();

            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(d);
            Assert.AreEqual(500, d.Index);
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestIndexAccessRandom()
        {
            //Assert.IsTrue(File.Exists(collection.Path));

            var sw = new Stopwatch();
            sw.Start();
            int index = new Random().Next(_sequentialDataCache.Count);

            MyData d = _sequentialDataCache[index];

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Console.WriteLine("index: {0}", index);
            Assert.IsNotNull(d);
            Assert.AreEqual(index, d.Index);
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestForeach()
        {
            //Assert.IsTrue(File.Exists(collection.Path));

            var sw = new Stopwatch();
            sw.Start();

            foreach (MyData myData in _sequentialDataCache)
            {
            }

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestLinqTake()
        {
            //Assert.IsTrue(File.Exists(collection.Path));
            var sw = new Stopwatch();
            sw.Start();

            MyData[] d = _sequentialDataCache.Take(20).ToArray();

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(d);
            Assert.AreEqual(20, d.Length);
            Assert.AreEqual(0, d[0].Index);
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestLinqTake20Parallel()
        {
            //Assert.IsTrue(File.Exists(collection.Path));

            //returns unordered item due to parallel
            var sw = new Stopwatch();
            sw.Start();

            MyData[] d = _sequentialDataCache.AsParallel().Take(20).ToArray();

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(d);
            Assert.AreEqual(20, d.Length);
            Assert.IsTrue(d.Any(c => c.Index == 0));
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestLinqSkipTake()
        {
            //Assert.IsTrue(File.Exists(collection.Path));
            var sw = new Stopwatch();
            sw.Start();

            MyData[] d = _sequentialDataCache.Skip(10).Take(20).ToArray();

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(d);
            Assert.AreEqual(20, d.Length);
            Assert.AreEqual(10, d[0].Index);
        }


        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestLinqSkipTakeParallel()
        {
            //Assert.IsTrue(File.Exists(collection.Path));
            var sw = new Stopwatch();
            sw.Start();

            MyData[] d = _sequentialDataCache.AsParallel().Skip(10).Take(20).ToArray();

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(d);
            Assert.AreEqual(20, d.Length);
            Assert.IsTrue(d.Any(c => c.Index == 10));
        }


        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestLinqWhereTake100()
        {
            //Assert.IsTrue(File.Exists(collection.Path));

            var sw = new Stopwatch();
            sw.Start();

            MyData[] d = _sequentialDataCache.Where(o => o.Index >= 500).Take(100).ToArray();


            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(d);
            Assert.AreEqual(100, d.Length);
            Assert.AreEqual(500, d[0].Index);
            Assert.AreEqual(599, d[99].Index);
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestLinqWhereTake100Parallel()
        {
            //Assert.IsTrue(File.Exists(collection.Path));

            var sw = new Stopwatch();
            sw.Start();

            MyData[] d = _sequentialDataCache.AsParallel().Where(o => o.Index >= 500).Take(100).ToArray();

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(d);
            Assert.AreEqual(100, d.Length);
            Assert.IsTrue(d.Any(c => c.Index == 500));
            Assert.IsTrue(d.Any(c => c.Index == 599));
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestLinqCount()
        {
            //Assert.IsTrue(File.Exists(collection.Path));

            var sw = new Stopwatch();
            sw.Start();

            int d = _sequentialDataCache.Count;

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(d);
            Assert.AreEqual(30000, _sequentialDataCache.Count);
            Assert.AreEqual(30000, d);
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestLinqSumDouble()
        {
            //Assert.IsTrue(File.Exists(collection.Path));
            var sw = new Stopwatch();
            sw.Start();

            double sum = _sequentialDataCache.Sum(a => a.TwoDimensionDoubleArray[0, 1]);

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(sum);
            Assert.AreEqual(29999, sum);
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestLinqSumDoubleParallel()
        {
            //Assert.IsTrue(File.Exists(collection.Path));
            var sw = new Stopwatch();
            sw.Start();

            double sum = _sequentialDataCache.AsParallel().Sum(a => a.TwoDimensionDoubleArray[0, 1]);

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(sum);
            Assert.AreEqual(29999, sum);
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestLinqAverageDouble()
        {
            //Assert.IsTrue(File.Exists(collection.Path));
            var sw = new Stopwatch();
            sw.Start();

            double average = _sequentialDataCache.Average(a => a.TwoDimensionDoubleArray[2, 2]);

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(average);
            Assert.AreEqual(2002, average);
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestLinqAverageDoubleParallel()
        {
            //Assert.IsTrue(File.Exists(collection.Path));
            var sw = new Stopwatch();
            sw.Start();

            double average = _sequentialDataCache.AsParallel().Average(a => a.TwoDimensionDoubleArray[2, 2]);

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

            Assert.IsNotNull(average);
            Assert.AreEqual(2002, average);
        }

        [TestMethod]
        [TestCategory("SequentialDataCache")]
        public void TestIncrementTableValuesPerformance()
        {
            //take 30000 random data, increment all table values, and finally return result.

            var rnd = new Random();
            //Assert.IsTrue(File.Exists(collection.Path));
            var sw = new Stopwatch();
            sw.Start();

            //increment random node values
            for (int i = 0; i < 30000; i++)
            {
                var index = rnd.Next(0, 30000);
                var d = _sequentialDataCache[i];

                //d.IncrementTableValuesUnsafe();
                d.IncrementTableValues();
            }

            //create result
            var result = _sequentialDataCache.ToSequentialDataCache();

            sw.Stop();
            Console.WriteLine("Ellapsed: {0}", sw.Elapsed);
            WriteResult(sw.Elapsed);

        }


        #region Nested type: MyData

        [Serializable]
        [DebuggerDisplay("MyData[{Index}]")]
        private class MyData
        {
            public int Index { get; set; }
            public Guid Id { get; set; }
            //public double[][] JaggedArray { get; set; }
            //public double[] OneDimensionArray { get; set; }
            public double[,] TwoDimensionDoubleArray { get; set; }
            //public decimal[,] TwoDimensionDecimalArray { get; set; }
            //public double[, ,] ThreeDimensionArray { get; set; }

            public void IncrementTableValues()
            {
                for (int i = 0; i < TwoDimensionDoubleArray.GetLength(0); i++)
                {
                    for (int j = 0; j < TwoDimensionDoubleArray.GetLength(1); j++)
                    {
                        TwoDimensionDoubleArray[i, j] += 1;
                    }
                }
            }

            public static double[] InitializeOneDimensionDoubleArray(int length1 = 3)
            {
                double v = 0;
                var d = new double[length1];
                for (int i = 0; i < length1; i++, v++)
                    d[i] = v;


                return d;
            }

            public static double[,] InitializeTwoDimensionDoubleArray(int length1 = 3, int length2 = 1000)
            {
                double v = 0;
                var d = new double[length1, length2];
                for (int i = 0; i < length1; i++)
                    for (int j = 0; j < length2; j++, v++)
                        d[i, j] = v;


                return d;
            }

            public static double[, ,] InitializeThreeDimensionDoubleArray(int length1 = 3, int length2 = 1000,
                                                                         int length3 = 2)
            {
                double v = 0;
                var d = new double[length1, length2, length3];
                for (int i = 0; i < length1; i++)
                    for (int j = 0; j < length2; j++)
                        for (int k = 0; k < length3; k++, v++)
                            d[i, j, k] = v;


                return d;
            }

            public static Decimal[] InitializeOneDimensionDecimalArray(int length1 = 3)
            {
                Decimal v = 0;
                var d = new Decimal[length1];
                for (int i = 0; i < length1; i++, v++)
                    d[i] = v;


                return d;
            }

            public static Decimal[,] InitializeTwoDimensionDecimalArray(int length1 = 3, int length2 = 1000)
            {
                Decimal v = 0;
                var d = new Decimal[length1, length2];
                for (int i = 0; i < length1; i++)
                    for (int j = 0; j < length2; j++, v++)
                        d[i, j] = v;


                return d;
            }

            public static Decimal[, ,] InitializeThreeDimensionDecimalArray(int length1 = 3, int length2 = 1000,
                                                                           int length3 = 2)
            {
                Decimal v = 0;
                var d = new Decimal[length1, length2, length3];
                for (int i = 0; i < length1; i++)
                    for (int j = 0; j < length2; j++)
                        for (int k = 0; k < length3; k++, v++)
                            d[i, j, k] = v;


                return d;
            }


            public static float[] InitializeOneDimensionFloatArray(int length1 = 3)
            {
                float v = 0;
                var d = new float[length1];
                for (int i = 0; i < length1; i++, v++)
                    d[i] = v;


                return d;
            }

            public static float[,] InitializeTwoDimensionFloatArray(int length1 = 3, int length2 = 1000)
            {
                float v = 0;
                var d = new float[length1, length2];
                for (int i = 0; i < length1; i++)
                    for (int j = 0; j < length2; j++, v++)
                        d[i, j] = v;


                return d;
            }

            public static float[, ,] InitializeThreeDimensionFloatArray(int length1 = 3, int length2 = 1000,
                                                                       int length3 = 2)
            {
                float v = 0;
                var d = new float[length1, length2, length3];
                for (int i = 0; i < length1; i++)
                    for (int j = 0; j < length2; j++)
                        for (int k = 0; k < length3; k++, v++)
                            d[i, j, k] = v;


                return d;
            }

            public static double[][] InitializeJaggedArray(int x = 3, int y = 1000)
            {
                var d = new double[x][];

                for (int i = 0; i < x; i++)
                {
                    d[i] = new double[y];
                    for (int j = 0; j < y; j++)
                        d[i][j] = i * j;
                }


                return d;
            }
        }

        #endregion
    }
}